<!DOCTYPE HTML>
<!--
	Dimension by HTML5 UP
	html5up.net | @ajlkn
	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
 <head>
  <title>
   Dimension by HTML5 UP
  </title>
  <!-- <meta charset="utf-8" /> -->
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> -->
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1.0" name="viewport"/>
  <link href="../../assets/css/article.css" rel="stylesheet"/>
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet"/>
  <noscript>
   <link href="../../assets/css/noscript.css" rel="stylesheet"/>
  </noscript>
 </head>
 <body>
  <div id="app">
  </div>
  <!-- built files will be auto injected -->
 </body>
 <body class="is-preload">
  <!-- Wrapper -->
  <div id="wrapper">
   <!-- Main -->
   <div id="main">
    <article id="article">
     <h1 id="spring-cloud-gateway-http">
      Spring cloud Gateway（二） 一个Http请求的流程解析
     </h1>
     <hr/>
     <h2 id="_1">
      简介
     </h2>
     <p>
      通过一个简单示例，debug出Spring Cloud Gateway的一个HTTP请求的处理流程
     </p>
     <h2 id="_2">
      思路整理
     </h2>
     <p>
      在上篇文章中大致了解了SpringCloudGateway的大致作用和关键模块（路由匹配和Filter），在这篇文章中将梳理出一个HTTP请求的处理链路
     </p>
     <p>
      目前先不关心其具体细节，主要梳理出其路由匹配的核心处理类，请求和响应的处理流向核心类
     </p>
     <p>
      需要的一些前置知识：netty相关使用经验，因为分析大量用到了netty相关概念的类比和猜测
     </p>
     <h3 id="_3">
      示例代码
     </h3>
     <p>
      配置启动一个简单的程序，请求转发到后台服务器，在请求和响应上添加一些东西，程序代码大致如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@SpringBootApplication</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Application</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">SpringApplication</span><span class="p">.</span><span class="na">run</span><span class="p">(</span><span class="n">Application</span><span class="p">.</span><span class="na">class</span><span class="p">,</span> <span class="n">args</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="n">RouteLocator</span> <span class="nf">myRoutes</span><span class="p">(</span><span class="n">RouteLocatorBuilder</span> <span class="n">builder</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">builder</span><span class="p">.</span><span class="na">routes</span><span class="p">()</span>
                <span class="p">.</span><span class="na">route</span><span class="p">(</span><span class="n">p</span> <span class="o">-&gt;</span> <span class="n">p</span><span class="p">.</span><span class="na">path</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span>
                        <span class="p">.</span><span class="na">filters</span><span class="p">(</span><span class="n">f</span> <span class="o">-&gt;</span> <span class="n">f</span><span class="p">.</span><span class="na">addRequestParameter</span><span class="p">(</span><span class="s">"test"</span><span class="p">,</span> <span class="s">"test"</span><span class="p">)</span>
                                <span class="p">.</span><span class="na">addResponseHeader</span><span class="p">(</span><span class="s">"return"</span><span class="p">,</span> <span class="s">"return"</span><span class="p">))</span>
                        <span class="p">.</span><span class="na">uri</span><span class="p">(</span><span class="s">"http://localhost:8082/"</span><span class="p">))</span>
                <span class="p">.</span><span class="na">build</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <h3 id="_4">
      相关处理类查找
     </h3>
     <h4 id="_5">
      路由匹配
     </h4>
     <p>
      首先在没有打任何断点的情况下运行一次程序：把程序跑起来，访问 http://localhost:8080/，成功得到结果。通过查看日志，发现有下面关于路由匹配的语句：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="err">o.s.c.g.h.p.RoutePredicateFactory        : Pattern "[/image/webp]" does not match against value "/"</span>
<span class="err">o.s.c.g.h.p.RoutePredicateFactory        : Pattern "/" matches against value "/"</span>
<span class="err">o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: 606b3b86-7ef4-4538-bbcb-b512c411c325</span>
</code></pre>
     </div>
     <p>
      一眼便看到 Route matched，还有类名带有 RoutePredicate,这大概率是路由匹配的核心处理类了，于是我们直接搜索打开这个类，看到下面的关键内容：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RoutePredicateHandlerMapping</span> <span class="kd">extends</span> <span class="n">AbstractHandlerMapping</span> <span class="p">{</span>
    <span class="c1">// 这 lookupRoute 方法名一看就是妥妥的路由查找</span>
    <span class="kd">protected</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Route</span><span class="o">&gt;</span> <span class="nf">lookupRoute</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="na">routeLocator</span><span class="p">.</span><span class="na">getRoutes</span><span class="p">()</span>
                <span class="c1">// individually filter routes so that filterWhen error delaying is not a</span>
                <span class="c1">// problem</span>
                <span class="p">.</span><span class="na">concatMap</span><span class="p">(</span><span class="n">route</span> <span class="o">-&gt;</span> <span class="n">Mono</span><span class="p">.</span><span class="na">just</span><span class="p">(</span><span class="n">route</span><span class="p">).</span><span class="na">filterWhen</span><span class="p">(</span><span class="n">r</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="c1">// add the current route we are testing</span>
                    <span class="n">exchange</span><span class="p">.</span><span class="na">getAttributes</span><span class="p">().</span><span class="na">put</span><span class="p">(</span><span class="n">GATEWAY_PREDICATE_ROUTE_ATTR</span><span class="p">,</span> <span class="n">r</span><span class="p">.</span><span class="na">getId</span><span class="p">());</span>
                    <span class="k">return</span> <span class="n">r</span><span class="p">.</span><span class="na">getPredicate</span><span class="p">().</span><span class="na">apply</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>
                <span class="p">})</span>
                        <span class="c1">// instead of immediately stopping main flux due to error, log and</span>
                        <span class="c1">// swallow it</span>
                        <span class="p">.</span><span class="na">doOnError</span><span class="p">(</span><span class="n">e</span> <span class="o">-&gt;</span> <span class="n">logger</span><span class="p">.</span><span class="na">error</span><span class="p">(</span>
                                <span class="s">"Error applying predicate for route: "</span> <span class="o">+</span> <span class="n">route</span><span class="p">.</span><span class="na">getId</span><span class="p">(),</span>
                                <span class="n">e</span><span class="p">))</span>
                        <span class="p">.</span><span class="na">onErrorResume</span><span class="p">(</span><span class="n">e</span> <span class="o">-&gt;</span> <span class="n">Mono</span><span class="p">.</span><span class="na">empty</span><span class="p">()))</span>
                <span class="c1">// .defaultIfEmpty() put a static Route not found</span>
                <span class="c1">// or .switchIfEmpty()</span>
                <span class="c1">// .switchIfEmpty(Mono.&lt;Route&gt;empty().log("noroute"))</span>
                <span class="p">.</span><span class="na">next</span><span class="p">()</span>
                <span class="c1">// TODO: error handling</span>
                <span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">route</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">logger</span><span class="p">.</span><span class="na">isDebugEnabled</span><span class="p">())</span> <span class="p">{</span>
                        <span class="n">logger</span><span class="p">.</span><span class="na">debug</span><span class="p">(</span><span class="s">"Route matched: "</span> <span class="o">+</span> <span class="n">route</span><span class="p">.</span><span class="na">getId</span><span class="p">());</span>
                    <span class="p">}</span>
                    <span class="n">validateRoute</span><span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">exchange</span><span class="p">);</span>
                    <span class="k">return</span> <span class="n">route</span><span class="p">;</span>
                <span class="p">});</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      路由匹配找到这个就差不多了，具体的比如路由如何加载、查找后如何调整到处理链，到后面在此处打断点，通过调用栈应该可以看到
     </p>
     <h3 id="filter">
      Filter处理
     </h3>
     <h4 id="request">
      Request处理发送
     </h4>
     <p>
      一样通过查看debug日志，可以看到下面明显的日志：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="err">o.s.c.g.handler.FilteringWebHandler      : Sorted gatewayFilterFactories: </span>
<span class="err">o.s.c.g.filter.RouteToRequestUrlFilter   : RouteToRequestUrlFilter start</span>
</code></pre>
     </div>
     <p>
      首先查看 RouteToRequestUrlFilter,发现其中没有明显的处理链（根据写网关的Filter的经验或者Netty的pipeline，应该入口是一个列表的循环处理或者定义的地方）。
     </p>
     <p>
      于是我们打开另外一个类： FilteringWebHandler ,很幸运，看到filters，还有其get方法，很像，大致如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">FilteringWebHandler</span> <span class="kd">implements</span> <span class="n">WebHandler</span> <span class="p">{</span>
    <span class="p">......</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">DefaultGatewayFilterChain</span> <span class="kd">implements</span> <span class="n">GatewayFilterChain</span> <span class="p">{</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">Mono</span><span class="p">.</span><span class="na">defer</span><span class="p">(()</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">index</span> <span class="o">&lt;</span> <span class="n">filters</span><span class="p">.</span><span class="na">size</span><span class="p">())</span> <span class="p">{</span>
                    <span class="n">GatewayFilter</span> <span class="n">filter</span> <span class="o">=</span> <span class="n">filters</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">index</span><span class="p">);</span>
                    <span class="n">DefaultGatewayFilterChain</span> <span class="n">chain</span> <span class="o">=</span> <span class="k">new</span> <span class="n">DefaultGatewayFilterChain</span><span class="p">(</span><span class="k">this</span><span class="p">,</span>
                            <span class="k">this</span><span class="p">.</span><span class="na">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
                    <span class="k">return</span> <span class="n">filter</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">,</span> <span class="n">chain</span><span class="p">);</span>
                <span class="p">}</span>
                <span class="k">else</span> <span class="p">{</span>
                    <span class="k">return</span> <span class="n">Mono</span><span class="p">.</span><span class="na">empty</span><span class="p">();</span> <span class="c1">// complete</span>
                <span class="p">}</span>
            <span class="p">});</span>
        <span class="p">}</span>

    <span class="p">}</span>

    <span class="p">......</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      这里应该就是Filter处理的核心了，接下来在这个函数进行打断点进行调试
     </p>
     <p>
      在程序debug停在此处的时候，我们可以查看 filters 的值是啥，大致的内容如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="err">0 RemoveCachedBodyFilter</span>
<span class="err">1 AdaptCachedBodyGlobalFilter</span>
<span class="err">2 NettyWriteResponseFilter</span>
<span class="err">3 ForwardPathFilter</span>
<span class="err">4 GatewayMetricsFilter</span>
<span class="err">5 [[AddRequestParameter test = 'test'], order = 0]</span>
<span class="err">6 [[AddResponseHeader return = 'return'], order = 0]</span>
<span class="err">7 RouteToRequestUrlFilter</span>
<span class="err">8 LoadBalancerClientFilter</span>
<span class="err">9 WebsocketRoutingFilter</span>
<span class="err">10 NettyRoutingFilter</span>
<span class="err">11 ForwardRoutingFilter</span>
</code></pre>
     </div>
     <p>
      此时我们沿着这条链路一直debug下去，进入上面所有的 filter 走一遍
     </p>
     <p>
      在这次的debug过程中，能清晰的看到每个类都走了一遍，但是在最后一个filter：ForwardRoutingFilter,它的关键代码如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ForwardRoutingFilter</span> <span class="kd">implements</span> <span class="n">GlobalFilter</span><span class="p">,</span> <span class="n">Ordered</span> <span class="p">{</span>
    <span class="p">......</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">,</span> <span class="n">GatewayFilterChain</span> <span class="n">chain</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">URI</span> <span class="n">requestUrl</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getRequiredAttribute</span><span class="p">(</span><span class="n">GATEWAY_REQUEST_URL_ATTR</span><span class="p">);</span>

        <span class="n">String</span> <span class="n">scheme</span> <span class="o">=</span> <span class="n">requestUrl</span><span class="p">.</span><span class="na">getScheme</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">isAlreadyRouted</span><span class="p">(</span><span class="n">exchange</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="s">"forward"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">scheme</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">chain</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="c1">// TODO: translate url?</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">log</span><span class="p">.</span><span class="na">isTraceEnabled</span><span class="p">())</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">trace</span><span class="p">(</span><span class="s">"Forwarding to URI: "</span> <span class="o">+</span> <span class="n">requestUrl</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="na">getDispatcherHandler</span><span class="p">().</span><span class="na">handle</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="p">......</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      目前的猜测是最后一个filter，发送请求到后台服务器，但是这个filter完全没有看到这个类似的代码，于是又瞎debug了第二遍和第三遍，发现了一个及其可疑的类: NettyRoutingFilter,在这个类中发现了请求发送相关代码，大致如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NettyRoutingFilter</span> <span class="kd">implements</span> <span class="n">GlobalFilter</span><span class="p">,</span> <span class="n">Ordered</span> <span class="p">{</span>
    <span class="p">......</span>

    <span class="nd">@Override</span>
    <span class="nd">@SuppressWarnings</span><span class="p">(</span><span class="s">"Duplicates"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">,</span> <span class="n">GatewayFilterChain</span> <span class="n">chain</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">URI</span> <span class="n">requestUrl</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getRequiredAttribute</span><span class="p">(</span><span class="n">GATEWAY_REQUEST_URL_ATTR</span><span class="p">);</span>

        <span class="n">String</span> <span class="n">scheme</span> <span class="o">=</span> <span class="n">requestUrl</span><span class="p">.</span><span class="na">getScheme</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">isAlreadyRouted</span><span class="p">(</span><span class="n">exchange</span><span class="p">)</span>
                <span class="o">||</span> <span class="p">(</span><span class="o">!</span><span class="s">"http"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">scheme</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="s">"https"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">scheme</span><span class="p">)))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">chain</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="p">......</span>

        <span class="n">Flux</span><span class="o">&lt;</span><span class="n">HttpClientResponse</span><span class="o">&gt;</span> <span class="n">responseFlux</span> <span class="o">=</span> <span class="n">getHttpClient</span><span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">exchange</span><span class="p">)</span>
                <span class="p">.</span><span class="na">headers</span><span class="p">(</span><span class="n">headers</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="n">headers</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">httpHeaders</span><span class="p">);</span>
                    <span class="c1">// Will either be set below, or later by Netty</span>
                    <span class="n">headers</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">HOST</span><span class="p">);</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">preserveHost</span><span class="p">)</span> <span class="p">{</span>
                        <span class="n">String</span> <span class="n">host</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="na">getHeaders</span><span class="p">().</span><span class="na">getFirst</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">HOST</span><span class="p">);</span>
                        <span class="n">headers</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">HOST</span><span class="p">,</span> <span class="n">host</span><span class="p">);</span>
                    <span class="p">}</span>
                <span class="p">}).</span><span class="na">request</span><span class="p">(</span><span class="n">method</span><span class="p">).</span><span class="na">uri</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="na">send</span><span class="p">((</span><span class="n">req</span><span class="p">,</span> <span class="n">nettyOutbound</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">log</span><span class="p">.</span><span class="na">isTraceEnabled</span><span class="p">())</span> <span class="p">{</span>
                        <span class="c1">///////////////////////////////////////////////////////////////////</span>
                        <span class="c1">// 熟悉的netty outbound</span>
                        <span class="n">nettyOutbound</span>
                                <span class="p">.</span><span class="na">withConnection</span><span class="p">(</span><span class="n">connection</span> <span class="o">-&gt;</span> <span class="n">log</span><span class="p">.</span><span class="na">trace</span><span class="p">(</span><span class="s">"outbound route: "</span>
                                        <span class="o">+</span> <span class="n">connection</span><span class="p">.</span><span class="na">channel</span><span class="p">().</span><span class="na">id</span><span class="p">().</span><span class="na">asShortText</span><span class="p">()</span>
                                        <span class="o">+</span> <span class="s">", inbound: "</span> <span class="o">+</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getLogPrefix</span><span class="p">()));</span>
                    <span class="p">}</span>
                    <span class="c1">// 使用outbound send 确定发送请求无疑了</span>
                    <span class="k">return</span> <span class="n">nettyOutbound</span><span class="p">.</span><span class="na">send</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getBody</span><span class="p">().</span><span class="na">map</span><span class="p">(</span><span class="k">this</span><span class="p">::</span><span class="n">getByteBuf</span><span class="p">));</span>
                    <span class="c1">/////////////////////////////////////////////////////////////////////////</span>
                <span class="p">}).</span><span class="na">responseConnection</span><span class="p">((</span><span class="n">res</span><span class="p">,</span> <span class="n">connection</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="p">......</span>
                <span class="p">});</span>

        <span class="p">......</span>
        <span class="k">return</span> <span class="n">responseFlux</span><span class="p">.</span><span class="na">then</span><span class="p">(</span><span class="n">chain</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">));</span>
    <span class="p">}</span>
    <span class="p">......</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在这里看到netty熟悉的outbound和send函数，那就确定是在这个类里面进行数据的发送，request的流程就走完了
     </p>
     <h4 id="response">
      Response响应处理
     </h4>
     <p>
      通过上面的步骤，我们找到了request的流程，但是发现好像把所有的filter都走完一遍了，如果类比到netty，那就应该有inbound和outbound。outbound的部分我们在上面找到了，现在要找inbound的部分。
     </p>
     <p>
      所有filter都走了一遍，代码中也没有类似inbound和outbound属性的判断，则假设这些filter是双工的，同时兼备inbound和outbound的类似功能。通过查看这些filter的代码，我们在下面那个地方找到了response相关的代码：
     </p>
     <p>
      在第二个类 NettyWriteResponseFilter,清晰的看到了熟悉的 writeAndFlushWith,到这里就确定了response响应的最后是到了writeAndFlushWith。但是，整个流程是怎么样的目前还是不清楚，没有看到链表逆序或者反向传播的相关代码
     </p>
     <p>
      判断当前有些关键的组件知识欠缺，在这些filter类代码中，仔细查看相关的代码，看着像是lamda表达式，但是核心思想是逐个调用处理，依照这个思路来梳理下面的代码：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NettyRoutingFilter</span> <span class="kd">implements</span> <span class="n">GlobalFilter</span><span class="p">,</span> <span class="n">Ordered</span> <span class="p">{</span>
    <span class="p">......</span>

    <span class="nd">@Override</span>
    <span class="nd">@SuppressWarnings</span><span class="p">(</span><span class="s">"Duplicates"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">,</span> <span class="n">GatewayFilterChain</span> <span class="n">chain</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">......</span>

        <span class="n">Flux</span><span class="o">&lt;</span><span class="n">HttpClientResponse</span><span class="o">&gt;</span> <span class="n">responseFlux</span> <span class="o">=</span> <span class="n">getHttpClient</span><span class="p">(</span><span class="n">route</span><span class="p">,</span> <span class="n">exchange</span><span class="p">)</span>
                <span class="c1">// 猜测设置header</span>
                <span class="p">.</span><span class="na">headers</span><span class="p">(</span><span class="n">headers</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="p">......</span>
                <span class="c1">// 发送request</span>
                <span class="p">}).</span><span class="na">request</span><span class="p">(</span><span class="n">method</span><span class="p">).</span><span class="na">uri</span><span class="p">(</span><span class="n">url</span><span class="p">).</span><span class="na">send</span><span class="p">((</span><span class="n">req</span><span class="p">,</span> <span class="n">nettyOutbound</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="p">......</span>
                <span class="c1">// 接收到返回的response</span>
                <span class="p">}).</span><span class="na">responseConnection</span><span class="p">((</span><span class="n">res</span><span class="p">,</span> <span class="n">connection</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="c1">// 这里看到将结果放入了，exchange中，而这个exchange贯穿了这个请求流程，感觉这个就类似netty的ctx</span>
                    <span class="n">exchange</span><span class="p">.</span><span class="na">getAttributes</span><span class="p">().</span><span class="na">put</span><span class="p">(</span><span class="n">CLIENT_RESPONSE_ATTR</span><span class="p">,</span> <span class="n">res</span><span class="p">);</span>
                    <span class="n">exchange</span><span class="p">.</span><span class="na">getAttributes</span><span class="p">().</span><span class="na">put</span><span class="p">(</span><span class="n">CLIENT_RESPONSE_CONN_ATTR</span><span class="p">,</span> <span class="n">connection</span><span class="p">);</span>

                    <span class="n">ServerHttpResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getResponse</span><span class="p">();</span>
                    <span class="c1">// put headers and status so filters can modify the response</span>
                    <span class="n">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HttpHeaders</span><span class="p">();</span>

                    <span class="n">res</span><span class="p">.</span><span class="na">responseHeaders</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span>
                            <span class="n">entry</span> <span class="o">-&gt;</span> <span class="n">headers</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="na">getKey</span><span class="p">(),</span> <span class="n">entry</span><span class="p">.</span><span class="na">getValue</span><span class="p">()));</span>

                    <span class="n">String</span> <span class="n">contentTypeValue</span> <span class="o">=</span> <span class="n">headers</span><span class="p">.</span><span class="na">getFirst</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">CONTENT_TYPE</span><span class="p">);</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">StringUtils</span><span class="p">.</span><span class="na">hasLength</span><span class="p">(</span><span class="n">contentTypeValue</span><span class="p">))</span> <span class="p">{</span>
                        <span class="n">exchange</span><span class="p">.</span><span class="na">getAttributes</span><span class="p">().</span><span class="na">put</span><span class="p">(</span><span class="n">ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR</span><span class="p">,</span>
                                <span class="n">contentTypeValue</span><span class="p">);</span>
                    <span class="p">}</span>

                    <span class="n">setResponseStatus</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="n">response</span><span class="p">);</span>

                    <span class="c1">// make sure headers filters run after setting status so it is</span>
                    <span class="c1">// available in response</span>
                    <span class="n">HttpHeaders</span> <span class="n">filteredResponseHeaders</span> <span class="o">=</span> <span class="n">HttpHeadersFilter</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span>
                            <span class="n">getHeadersFilters</span><span class="p">(),</span> <span class="n">headers</span><span class="p">,</span> <span class="n">exchange</span><span class="p">,</span> <span class="n">Type</span><span class="p">.</span><span class="na">RESPONSE</span><span class="p">);</span>

                    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">filteredResponseHeaders</span>
                            <span class="p">.</span><span class="na">containsKey</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">TRANSFER_ENCODING</span><span class="p">)</span>
                            <span class="o">&amp;&amp;</span> <span class="n">filteredResponseHeaders</span>
                                    <span class="p">.</span><span class="na">containsKey</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">CONTENT_LENGTH</span><span class="p">))</span> <span class="p">{</span>
                        <span class="c1">// It is not valid to have both the transfer-encoding header and</span>
                        <span class="c1">// the content-length header.</span>
                        <span class="c1">// Remove the transfer-encoding header in the response if the</span>
                        <span class="c1">// content-length header is present.</span>
                        <span class="n">response</span><span class="p">.</span><span class="na">getHeaders</span><span class="p">().</span><span class="na">remove</span><span class="p">(</span><span class="n">HttpHeaders</span><span class="p">.</span><span class="na">TRANSFER_ENCODING</span><span class="p">);</span>
                    <span class="p">}</span>

                    <span class="n">exchange</span><span class="p">.</span><span class="na">getAttributes</span><span class="p">().</span><span class="na">put</span><span class="p">(</span><span class="n">CLIENT_RESPONSE_HEADER_NAMES</span><span class="p">,</span>
                            <span class="n">filteredResponseHeaders</span><span class="p">.</span><span class="na">keySet</span><span class="p">());</span>

                    <span class="n">response</span><span class="p">.</span><span class="na">getHeaders</span><span class="p">().</span><span class="na">putAll</span><span class="p">(</span><span class="n">filteredResponseHeaders</span><span class="p">);</span>

                    <span class="k">return</span> <span class="n">Mono</span><span class="p">.</span><span class="na">just</span><span class="p">(</span><span class="n">res</span><span class="p">);</span>
                <span class="p">});</span>

        <span class="k">return</span> <span class="n">responseFlux</span><span class="p">.</span><span class="na">then</span><span class="p">(</span><span class="n">chain</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>



<span class="c1">// 在下面这个类的filter方法中，我们看到response的获取</span>
<span class="c1">// 看到了属性的writeAndFlushWith</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">NettyWriteResponseFilter</span> <span class="kd">implements</span> <span class="n">GlobalFilter</span><span class="p">,</span> <span class="n">Ordered</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">,</span> <span class="n">GatewayFilterChain</span> <span class="n">chain</span><span class="p">)</span> <span class="p">{</span>
        <span class="c1">// 通过debug可以看到 filter是触发下一个filter类的执行，filter执行完后执行then，里面有明显的发送响应到客户端的代码</span>
        <span class="k">return</span> <span class="n">chain</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">)</span>
                <span class="p">.</span><span class="na">doOnError</span><span class="p">(</span><span class="n">throwable</span> <span class="o">-&gt;</span> <span class="n">cleanup</span><span class="p">(</span><span class="n">exchange</span><span class="p">))</span>
                <span class="p">.</span><span class="na">then</span><span class="p">(</span><span class="n">Mono</span><span class="p">.</span><span class="na">defer</span><span class="p">(()</span> <span class="o">-&gt;</span> <span class="p">{</span>
                    <span class="n">Connection</span> <span class="n">connection</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getAttribute</span><span class="p">(</span><span class="n">CLIENT_RESPONSE_CONN_ATTR</span><span class="p">);</span>

                    <span class="k">if</span> <span class="p">(</span><span class="n">connection</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">return</span> <span class="n">Mono</span><span class="p">.</span><span class="na">empty</span><span class="p">();</span>
                    <span class="p">}</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">log</span><span class="p">.</span><span class="na">isTraceEnabled</span><span class="p">())</span> <span class="p">{</span>
                        <span class="n">log</span><span class="p">.</span><span class="na">trace</span><span class="p">(</span><span class="s">"NettyWriteResponseFilter start inbound: "</span>
                                <span class="o">+</span> <span class="n">connection</span><span class="p">.</span><span class="na">channel</span><span class="p">().</span><span class="na">id</span><span class="p">().</span><span class="na">asShortText</span><span class="p">()</span> <span class="o">+</span> <span class="s">", outbound: "</span>
                                <span class="o">+</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getLogPrefix</span><span class="p">());</span>
                    <span class="p">}</span>
                    <span class="n">ServerHttpResponse</span> <span class="n">response</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getResponse</span><span class="p">();</span>

                    <span class="c1">// TODO: needed?</span>
                    <span class="kd">final</span> <span class="n">Flux</span><span class="o">&lt;</span><span class="n">DataBuffer</span><span class="o">&gt;</span> <span class="n">body</span> <span class="o">=</span> <span class="n">connection</span>
                            <span class="p">.</span><span class="na">inbound</span><span class="p">()</span>
                            <span class="p">.</span><span class="na">receive</span><span class="p">()</span>
                            <span class="p">.</span><span class="na">retain</span><span class="p">()</span>
                            <span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">byteBuf</span> <span class="o">-&gt;</span> <span class="n">wrap</span><span class="p">(</span><span class="n">byteBuf</span><span class="p">,</span> <span class="n">response</span><span class="p">));</span>

                    <span class="n">MediaType</span> <span class="n">contentType</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
                    <span class="k">try</span> <span class="p">{</span>
                        <span class="n">contentType</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="na">getHeaders</span><span class="p">().</span><span class="na">getContentType</span><span class="p">();</span>
                    <span class="p">}</span>
                    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">log</span><span class="p">.</span><span class="na">isTraceEnabled</span><span class="p">())</span> <span class="p">{</span>
                            <span class="n">log</span><span class="p">.</span><span class="na">trace</span><span class="p">(</span><span class="s">"invalid media type"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                    <span class="k">return</span> <span class="p">(</span><span class="n">isStreamingMediaType</span><span class="p">(</span><span class="n">contentType</span><span class="p">)</span>
                            <span class="o">?</span> <span class="n">response</span><span class="p">.</span><span class="na">writeAndFlushWith</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Flux</span><span class="p">::</span><span class="n">just</span><span class="p">))</span>
                            <span class="p">:</span> <span class="n">response</span><span class="p">.</span><span class="na">writeWith</span><span class="p">(</span><span class="n">body</span><span class="p">));</span>
                <span class="p">})).</span><span class="na">doOnCancel</span><span class="p">(()</span> <span class="o">-&gt;</span> <span class="n">cleanup</span><span class="p">(</span><span class="n">exchange</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      到这我们就大致摸清楚了response响应的数据流向
     </p>
     <h4 id="_6">
      请求数据流向
     </h4>
     <p>
      数据流图如下，图中使用序号代替filter处理器
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="err">0 RemoveCachedBodyFilter</span>
<span class="err">1 AdaptCachedBodyGlobalFilter</span>
<span class="err">2 NettyWriteResponseFilter</span>
<span class="err">3 ForwardPathFilter</span>
<span class="err">4 GatewayMetricsFilter</span>
<span class="err">5 AddRequestParameterGatewayFilterFactory -- [[AddRequestParameter test = 'test'], order = 0]</span>
<span class="err">6 AddResponseHeaderGatewayFilterFactory -- [[AddResponseHeader return = 'return'], order = 0]</span>
<span class="err">7 RouteToRequestUrlFilter</span>
<span class="err">8 LoadBalancerClientFilter</span>
<span class="err">9 WebsocketRoutingFilter</span>
<span class="err">10 NettyRoutingFilter</span>
<span class="err">11 ForwardRoutingFilter</span>
</code></pre>
     </div>
     <p>
      <img alt="" src="./picture/resAndresponLine.png"/>
     </p>
     <p>
      通过debug梳理下来，我们大致看到数据如何在整个filter链中流动的。从中也大致体会到响应式编程的一点点思想，感觉还挺好用
     </p>
     <p>
      AddResponseHeaderGatewayFilterFactory 添加 response的 header，通过查看代码，发现思想还有点巧妙：在 exchange 中初始化了响应response，所有的响应的相应的修改都在这个上面进行修改，在拿到服务器的响应以后，直接将这些放到里面去。这个思想还挺巧妙的，很有借鉴意义
     </p>
     <h3 id="_7">
      总结
     </h3>
     <p>
      request每个filter都会走一遍，但并不是每个filter都会对request进行处理，类似下面的代码，通过判断是不是websocket的前缀来判断是否需要进行处理。这里好像通过这个实现了不同请求类型的转发，也就是 HTTP（NettyRoutingFilter）和 Web（WebsocketRoutingFilter）结构上应该是同级的，这个思想挺有意思
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">WebsocketRoutingFilter</span> <span class="kd">implements</span> <span class="n">GlobalFilter</span><span class="p">,</span> <span class="n">Ordered</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="n">Mono</span><span class="o">&lt;</span><span class="n">Void</span><span class="o">&gt;</span> <span class="nf">filter</span><span class="p">(</span><span class="n">ServerWebExchange</span> <span class="n">exchange</span><span class="p">,</span> <span class="n">GatewayFilterChain</span> <span class="n">chain</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">changeSchemeIfIsWebSocketUpgrade</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>

        <span class="n">URI</span> <span class="n">requestUrl</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getRequiredAttribute</span><span class="p">(</span><span class="n">GATEWAY_REQUEST_URL_ATTR</span><span class="p">);</span>
        <span class="n">String</span> <span class="n">scheme</span> <span class="o">=</span> <span class="n">requestUrl</span><span class="p">.</span><span class="na">getScheme</span><span class="p">();</span>

        <span class="c1">// 这里对请求的类型进行判断</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">isAlreadyRouted</span><span class="p">(</span><span class="n">exchange</span><span class="p">)</span>
                <span class="o">||</span> <span class="p">(</span><span class="o">!</span><span class="s">"ws"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">scheme</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="s">"wss"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">scheme</span><span class="p">)))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="n">chain</span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">setAlreadyRouted</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span>

        <span class="n">HttpHeaders</span> <span class="n">headers</span> <span class="o">=</span> <span class="n">exchange</span><span class="p">.</span><span class="na">getRequest</span><span class="p">().</span><span class="na">getHeaders</span><span class="p">();</span>
        <span class="n">HttpHeaders</span> <span class="n">filtered</span> <span class="o">=</span> <span class="n">filterRequest</span><span class="p">(</span><span class="n">getHeadersFilters</span><span class="p">(),</span> <span class="n">exchange</span><span class="p">);</span>

        <span class="n">List</span><span class="o">&lt;</span><span class="n">String</span><span class="o">&gt;</span> <span class="n">protocols</span> <span class="o">=</span> <span class="n">headers</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">SEC_WEBSOCKET_PROTOCOL</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">protocols</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">protocols</span> <span class="o">=</span> <span class="n">headers</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">SEC_WEBSOCKET_PROTOCOL</span><span class="p">).</span><span class="na">stream</span><span class="p">().</span><span class="na">flatMap</span><span class="p">(</span>
                    <span class="n">header</span> <span class="o">-&gt;</span> <span class="n">Arrays</span><span class="p">.</span><span class="na">stream</span><span class="p">(</span><span class="n">commaDelimitedListToStringArray</span><span class="p">(</span><span class="n">header</span><span class="p">)))</span>
                    <span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">String</span><span class="p">::</span><span class="n">trim</span><span class="p">).</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">toList</span><span class="p">());</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="na">webSocketService</span><span class="p">.</span><span class="na">handleRequest</span><span class="p">(</span><span class="n">exchange</span><span class="p">,</span> <span class="k">new</span> <span class="n">ProxyWebSocketHandler</span><span class="p">(</span>
                <span class="n">requestUrl</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="na">webSocketClient</span><span class="p">,</span> <span class="n">filtered</span><span class="p">,</span> <span class="n">protocols</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      还有一个就是 response 修改的实现也是很巧妙，感觉收获了很多
     </p>
    </article>
   </div>
   <!-- Footer -->
   <footer id="footer">
    <p class="copyright">
     © Untitled. Design:
     <a href="https://html5up.net">
      HTML5 UP
     </a>
     .
    </p>
   </footer>
  </div>
  <!-- BG -->
  <div id="bg">
  </div>
  <!-- Scripts -->
  <script src="../assets/js/jquery.min.js">
  </script>
  <script src="../assets/js/browser.min.js">
  </script>
  <script src="../assets/js/breakpoints.min.js">
  </script>
  <script src="../assets/js/util.js">
  </script>
  <script src="../assets/js/main.js">
  </script>
 </body>
</html>
